/**
 *  @file IntCheck.cpp
 */
#include "IntCheck.h"
#include "../convert/Convert.h"

namespace cppflib
{

namespace lang
{

/**
 *  @return true if operation overflow (can't be representable by a int)
 */
_DLLAPI bool CheckOverflow(arithop_t op, int a, int b)
{
   bool isOverflow = false;

   if (op == lang::AOP_ADD) {
      // won't overflow if a and b are different signs
      if (a >= 0 && b >= 0) {
         if (static_cast<u32_t>(a) + static_cast<u32_t>(b) > 0x7fffffff)
            isOverflow = true;
      }
      else if (a < 0 && b < 0) {
         u64_t ta = static_cast<u64_t>(static_cast<u32_t>(-a));
         u64_t tb = static_cast<u64_t>(static_cast<u32_t>(-b));
         if (ta + tb > U64N(0x80000000))
            isOverflow = true;
      }
   }
   else if (op == lang::AOP_SUB) {
      // won't overflow if a and b are same signs
      if (a >= 0 && b < 0) {
         u32_t tb = -b;
         if (static_cast<u32_t>(a) + tb > 0x7fffffff)
            isOverflow = true;
      }
      else if (a < 0 && b >= 0) {
         u32_t ta = -a;
         if (ta + static_cast<u32_t>(b) > 0x80000000)
            isOverflow = true;
      }
   }
   else if (op == lang::AOP_MUL) {
      if (a >= 0 && b >= 0) {
         i64_t ta = static_cast<i64_t>(a);
         i64_t tb = static_cast<i64_t>(b);
         if (ta * tb > I64N(0x7fffffff))
            isOverflow = true;
      } 
      else if (a < 0 && b < 0) {
         u64_t ta = static_cast<u64_t>(static_cast<u32_t>(-a));
         u64_t tb = static_cast<u64_t>(static_cast<u32_t>(-b));
         if (ta * tb > U64N(0x7fffffff))
            isOverflow = true;
      } 
      else if ((a < 0 && b >= 0) || (a >= 0 && b < 0)) {
         u64_t ta = static_cast<u64_t>(a < 0 ? static_cast<u32_t>(-a) : a);
         u64_t tb = static_cast<u64_t>(b < 0 ? static_cast<u32_t>(-b) : b);
         if (ta * tb > U64N(0x80000000))
            isOverflow = true;
      } 
   }

   return isOverflow;
}

/**
 *  @return true if operation overflow (can't be representable by a u32_t)
 */
_DLLAPI bool CheckOverflow(arithop_t op, u32_t a, u32_t b)
{
   bool isOverflow = false;

   if (op == lang::AOP_ADD) {
      u64_t ta = static_cast<u64_t>(a);
      u64_t tb = static_cast<u64_t>(b);
      if (ta + tb > U64N(0xffffffff))
         isOverflow = true;
   }
   else if (op == lang::AOP_SUB) {
      if (a < b)
         isOverflow = true;
   }
   else if (op == lang::AOP_MUL) {
      
      u64_t ta = static_cast<u64_t>(a);
      u64_t tb = static_cast<u64_t>(b);
      if (ta * tb > U64N(0xffffffff))
         isOverflow = true;
   }

   return isOverflow;
}

/**
 *  @return true if operation overflow (can't be representable by a i64_t)
 */
_DLLAPI bool CheckOverflow(arithop_t op, i64_t a, i64_t b)
{
   bool isOverflow = false;

   if (op == lang::AOP_ADD) {
      // won't overflow if a and b are different signs
      if (a >= 0 && b >= 0) {
         if (static_cast<u64_t>(a) + static_cast<u64_t>(b) > U64N(0x7fffffffffffffff))
            isOverflow = true;
      }
      else if (a < 0 && b < 0) {
         u64_t ta = -a;
         u64_t tb = -b;

         if (ta >= U64N(0x8000000000000000) && tb >= U64N(0x8000000000000000)) {
            isOverflow = true;
         }
         else if (ta + tb > U64N(0x8000000000000000)) {
            isOverflow = true;
         }
      }
   }
   else if (op == lang::AOP_SUB) {
      // won't overflow if a and b are same signs
      if (a >= 0 && b < 0) {
         u64_t tb = -b;
         if (static_cast<u64_t>(a) + tb > U64N(0x7fffffffffffffff))
            isOverflow = true;
      }
      else if (a < 0 && b >= 0) {
         u64_t ta = -a;
         if (ta + static_cast<u64_t>(b) > U64N(0x8000000000000000))
            isOverflow = true;
      }
   }
   else if (op == lang::AOP_MUL) {
      /*
       *  a * b 
       *     => (high(a) shift32 + low(a)) * (high(b) shift32 + low(b))
       *     => high(a) high(b) shift32 shift32 + high(a) low(b) shift32 + high(b) low(a) shift32 + low(b) low(a)
       */
      u64_t ta = a < 0 ? -a : a;
      u64_t tb = b < 0 ? -b : b;
      u64_t highA = ta >> 32;
      u64_t lowA = ta & U64N(0xffffffff);
      u64_t highB = tb >> 32;
      u64_t lowB = tb & U64N(0xffffffff);

      do {
         if (highA != 0 && highB != 0) {
            isOverflow = true;
            break;
         }

         // compute last term -- low(b) low(a)
         u64_t rllba = lowB * lowA;

         if (rllba > U64N(0x8000000000000000)) {
            isOverflow = true;
            break;
         }

      
         // either highA or highB must be 0 here
         u64_t rhlba = 0;
         if (highA == 0) {

            // compute 3rd term -- high(b) low(a) shift32
            rhlba = highB * lowA;
            if (rhlba > U64N(0x80000000)) {
               isOverflow = true;
               break;
            }
            rhlba <<= 32;
            
         }
         else if (highB == 0) {
            
            // compute 2nd term -- high(a) low(b) shift32
            rhlba = highA * lowB;
            if (rhlba > U64N(0x80000000)) {
               isOverflow = true;
               break;
            }
            rhlba <<= 32;
         }

         // too big (this case is not checked yet until here)
         if (rhlba >= U64N(0x8000000000000000) && rllba >= U64N(0x8000000000000000)) {
            isOverflow = true;
            break;
         }

         if ((a >= 0 && b >= 0) || (a < 0 && b < 0)) { 
            // a and b are of the same sign
            if (rhlba + rllba > U64N(0x7fffffffffffffff)) {
               isOverflow = true;
               break;
            }
         }
         else {
            // a and b are different sign
            if (rhlba + rllba > U64N(0x8000000000000000)) {
               isOverflow = true;
               break;
            }
         }
      
      } while(false);

   }

   return isOverflow;
}


/**
 *  @return true if operation overflow (can't be representable by a u64_t)
 */
_DLLAPI bool CheckOverflow(arithop_t op, u64_t a, u64_t b)
{
   bool isOverflow = false;

   if (op == lang::AOP_ADD) {
      /*
       *  a + b 
       *     => highA shift32 + highB shift32 + lowA + lowB
       */
      u64_t highA = a >> 32;
      u64_t lowA = a & U64N(0xffffffff);
      u64_t highB = b >> 32;
      u64_t lowB = b & U64N(0xffffffff);

      do {
         // compute high term
         u64_t highRet = highA + highB;
         if (highRet > U64N(0xffffffff)) {
            isOverflow = true;
            break;
         }

         // compute low term
         u64_t lowRet = lowA + lowB;
         u64_t carry = lowRet >> 32;

         if (highRet + carry > U64N(0xffffffff)) {
            isOverflow = true;
            break;
         } 

      } while(false);
   }
   else if (op == lang::AOP_SUB) {
      if (a < b)
         isOverflow = true;
   }
   else if (op == lang::AOP_MUL) {
      /*
       *  a * b 
       *     => (high(a) shift32 + low(a)) * (high(b) shift32 + low(b))
       *     => high(a) high(b) shift32 shift32 + high(a) low(b) shift32 + high(b) low(a) shift32 + low(b) low(a)
       */
      u64_t highA = a >> 32;
      u64_t lowA = a & U64N(0xffffffff);
      u64_t highB = b >> 32;
      u64_t lowB = b & U64N(0xffffffff);

      do {
         if (highA != 0 && highB != 0) {
            isOverflow = true;
            break;
         }

         // compute last term -- low(b) low(a)
         u64_t rllba = lowB * lowA;

         // either highA or highB must be 0 here
         u64_t rhlba = 0;
         if (highA == 0) {

            // compute 3rd term -- high(b) low(a) shift32
            rhlba = highB * lowA;
            if (rhlba > U64N(0xffffffff)) {
               isOverflow = true;
               break;
            }
            rhlba <<= 32;
            
         }
         else if (highB == 0) {
            
            // compute 2nd term -- high(a) low(b) shift32
            rhlba = highA * lowB;
            if (rhlba > U64N(0xffffffff)) {
               isOverflow = true;
               break;
            }
            rhlba <<= 32;
         }

         // final check
         isOverflow = CheckOverflow(lang::AOP_ADD, rhlba, rllba);

      } while(false);

   }

   return isOverflow;
}

//! parsing state for checking a number
typedef enum _chknumparse
{
   FP_START, 
   FP_AFTERNEGSIGN,
   FP_BEFOREPOINT_1,
   FP_BEFOREPOINT_2,
   FP_AFTERPOINT_1,
   FP_AFTERPOINT_2,
   FP_AT_E,
   FP_EXPONENT_1,
   FP_EXPONENT_2
} chknumparse_t;

PRIVATE bool __IsWhiteSpaceOrStrEnd(const wc_t c)
{
   return (c == _S('\0') || c == _S('\r') || c == _S('\n') || c == _S('\t') || c == _S(' '));
}

/**
 *  Try parsing the input as if it was a floating point number or integer
 *
 *  @return NUMT_UNKNOWN -- cannot recognize
 *  @return NUMT_SINT32 -- an integer (32 bit or 64 bit)
 *  @return NUMT_DOUBLE -- double
 */
PRIVATE numbertype_t __TryParseFloating(pcwstr_t str)
{
   numbertype_t numType = NUMT_UNKNOWN;
   chknumparse_t fParseState = FP_START;
   bool isCont = true;
   
   // loop to check if it is INT32 or double
   while (isCont) {
      wc_t c = *str++;
      
      switch(fParseState) {
         case FP_START:
            if (c == _S('-')) {
               fParseState = FP_AFTERNEGSIGN;
            }
            else if (c >= _S('0') && c <= _S('9')) {
               fParseState = FP_AFTERNEGSIGN;
               --str;
            }
            else {
               isCont = false;    // error
            }
            break;
         case FP_AFTERNEGSIGN:
            if (c == _S('0')) {
               fParseState = FP_BEFOREPOINT_2;
            }
            else if (c >= _S('1') && c <= _S('9')) {
               fParseState = FP_BEFOREPOINT_1;
            }
            else {
               isCont = false;   // error
            }
            break;
         case FP_BEFOREPOINT_1:
            if (c >= _S('0') && c <= _S('9')) {
               // do nothing
            }
            else if (c == _S('.')) {
               fParseState = FP_BEFOREPOINT_2;
               --str;
            }
            else if (c == _S('e') || c == _S('E')) {
               fParseState = FP_AT_E;
            }
            else if (__IsWhiteSpaceOrStrEnd(c)) {
               numType = NUMT_SINT32; // assume to be int32 at this moment
               isCont = false;
            }
            else {
               isCont = false;    // error
            }
            break;
         case FP_BEFOREPOINT_2:
            if (c == _S('.')) {
               fParseState = FP_AFTERPOINT_1;
            }
            else if (c == _S('e') || c == _S('E')) {
               fParseState = FP_AT_E;
            }
            else if (__IsWhiteSpaceOrStrEnd(c)) {
               numType = NUMT_SINT32; // assume to be int32 at this moment
               isCont = false;
            }
            else {
               isCont = false;  // error
            }
            break;
         case FP_AFTERPOINT_1:
            if (c >= _S('0') && c <= _S('9')) {
               fParseState = FP_AFTERPOINT_2;
            }
            else {
               isCont = false;     // error
            }
            break;
         case FP_AFTERPOINT_2:
            if (c >= _S('0') && c <= _S('9')) {
               fParseState = FP_AFTERPOINT_2;
            }
            else if (c == _S('e') || c == _S('E')) {
               fParseState = FP_AT_E;
            }
            else if (__IsWhiteSpaceOrStrEnd(c)) {
               numType = NUMT_DOUBLE;
               isCont = false;
            }
            else {
               isCont = false;     // error
            }
            break;
         case FP_AT_E:
            if (c >= _S('0') && c <= _S('9')) {
               fParseState = FP_EXPONENT_1;
               --str;
            }
            else if (c == _S('+') || c == _S('-')) {
               fParseState = FP_EXPONENT_1;
            }
            else {
               isCont = false;  // error
            }
            break;
         case FP_EXPONENT_1:
            if (c >= _S('0') && c <= _S('9')) {
               fParseState = FP_EXPONENT_2;
            }
            else {
               isCont = false;  // error
            }
            break;
         case FP_EXPONENT_2:
            if (c >= _S('0') && c <= _S('9')) {
               // do nothing
            }
            else if (__IsWhiteSpaceOrStrEnd(c)) {
               numType = NUMT_DOUBLE;
               isCont = false;
            }
            else {
               isCont = false;  // error
            }
            break;
      } // end of switch

   } // end of while

   return numType;
}

/**
 *  Check whether the input string is an integer, floating point or any other.
 *  The checking algorithm here for floating point is tighter than that in convert::ToFloatNum
 *
 *  @return type of number
 */
_DLLAPI numbertype_t CheckNumType(pcwstr_t str)
{
   numbertype_t numType = NUMT_UNKNOWN;

   if (!str)
      return numType;

   numType = __TryParseFloating(str);
   
   if (numType == NUMT_SINT32) {
      // further check int32 or int64
      bool isOverflow = false;
      convert::ToInt32(str, isOverflow);
      if (isOverflow) {
         convert::ToInt64(str, isOverflow);
         numType = isOverflow ? NUMT_SINT64OVERFLOW : NUMT_SINT64;
      }
   }
   
   return numType;
}

}  // end of namespace lang

}  // end of namespace cppflib
